unit uxSettings;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls,
Dialogs, StrUtils, Registry, uxCtrls;

//------------------------------------------------------------------------------
const

CMD_SEND1  = 1;
CMD_RECV1  = 2;
CMD_SEND2  = 3;
CMD_RECV2  = 4;

CMD_FIRST  = CMD_SEND1;
CMD_LAST   = CMD_RECV2;

//------------------------------------------------------------------------------
type

TPattern = array [0..64] of Byte;

//------------------------------------------------------------------------------
TBreakPoint = record
public
  Addr: Cardinal;
  Pattern: TPattern;
end;
PBreakPoint = ^TBreakPoint;

//------------------------------------------------------------------------------
TSettings = record
private
  FileName: String;
  BPOriginal: array [CMD_FIRST..CMD_LAST] of Cardinal;
  procedure SetBPA(Index: Integer; T: String); // Address
  procedure SetBPT(Index: Integer; T: String); // Pattern
public
  BreakPoints: array [CMD_FIRST..CMD_LAST] of TBreakPoint;
  procedure Create(FileName: String);
  function GetAddrText(Index: Integer): String;
  function GetPatternText(Index: Integer): String;
  function Load: Boolean;
  function Save: Boolean;
end;

//------------------------------------------------------------------------------
type
TSettingsDialog = class(TForm)
  BtnBrowse: TButtonEx;
  BtnSave: TButtonEx;
  BtnUpdate: TButtonEx;
  BP1: TEdit;
  BP2: TEdit;
  BP3: TEdit;
  BP4: TEdit;
  Pattern1: TEdit;
  Pattern2: TEdit;
  Pattern3: TEdit;
  Pattern4: TEdit;
  Path: TEdit;
  OpenDialog: TOpenDialog;
  LABEL_ADDR: TLabelEx;
  LABEL_SEARCHPTN: TLabelEx;
  procedure BtnBrowse_OnClick(Sender: TObject);
  procedure BtnSave_OnClick(Sender: TObject);
  procedure BtnUpdate_OnClick(Sender: TObject);
  procedure FormCreate(Sender: TObject);
private
  BPCtrls: array [CMD_FIRST..CMD_LAST] of TEdit;
  PTCtrls: array [CMD_FIRST..CMD_LAST] of TEdit;
end;

//------------------------------------------------------------------------------
var

Settings: TSettings;
SettingsDialog: TSettingsDialog;
ThaiClientExe: String;

implementation

{$R *.dfm}

uses uxMain;

//------------------------------------------------------------------------------
const

SettingsAttrib: array [CMD_FIRST..CMD_LAST, 1..2] of String =
  (
    ('SEND1::BPA=', 'SEND1::BPT='),
    ('RECV1::BPA=', 'RECV1::BPT='),
    ('SEND2::BPA=', 'SEND2::BPT='),
    ('RECV2::BPA=', 'RECV2::BPT=')
  );

UpdateColor: array [Boolean] of TColor = (clRed, clBlue);

//------------------------------------------------------------------------------
type

TScanBuffer = record
private
  Bin: PByte;
  PRawData: PByte;
  SizeOfRawData: Cardinal;
  VirtualAddr: Cardinal;
  FCreated: Boolean;
public
  procedure Create(FileName: String);
  procedure Free;
  function Search(Pattern: PByte; PatternSize: Cardinal): Cardinal;
  property Created: Boolean read FCreated;
end;

//------------------------------------------------------------------------------
//** TScanBuffer
//------------------------------------------------------------------------------
procedure TScanBuffer.Create(FileName: String);
var
  F: File;
  Size: Integer;
  P: PByte;
  ImageBase, RVA: Cardinal;
begin
  FCreated := True;
  Bin := nil;
  FileMode := fmOpenRead or fmShareDenyNone;
  AssignFile(F, FileName);
  try
    Reset(F, 1);
    try
      Size :=  FileSize(F);
      GetMem(Bin, Size);
      P := Bin;
      try
        BlockRead(F, Bin^, Size);
        P := Bin + PCardinal(P + 60)^; // P -> PE Signature
        ImageBase := PCardinal(P + 52)^;
        RVA := PCardinal(P + 260)^;
        VirtualAddr := ImageBase + RVA;
        SizeOfRawData := PCardinal(P + 264)^;
        PRawData := Bin + PCardinal(P + 268)^;
      except
        FCreated := False;
      end;
    finally
      CloseFile(F);
    end;
  except
    FCreated := False;
  end;

  if not FCreated then
  begin
    if Bin <> nil then
    begin
      FreeMem(Bin);
      Bin := nil;
      SizeOfRawData := 0;
      PRawData := nil;
    end;
  end;
end;
//------------------------------------------------------------------------------
procedure TScanBuffer.Free;
begin
  if Bin <> nil then FreeMem(Bin);
end;
//------------------------------------------------------------------------------
function TScanBuffer.Search(Pattern: PByte; PatternSize: Cardinal): Cardinal;
const
  // Function Header
  //    push ebp
  //    mov ebp, esp
  FxHead: array [1..3] of Byte = ($55, $8B, $EC);
var
  P: PByte;
  i, k, Mark: Cardinal;
  Count: Integer;
begin
  Result := Cardinal(-1);

  if PatternSize = 0 then Exit;

  P := PRawData;
  Count := 0;
  Mark := 0;

  for i := 0 to SizeOfRawData - PatternSize do
    if CompareMem(P + i, Pattern, PatternSize) then
    begin
      Inc(Count);
      Mark := i;
    end;

  if (Count = 1) and (Mark > 1000) then
  begin
    i := Mark - 1000;

    for k := Mark downto i do
      if CompareMem(P + k, @FxHead, 3) then Exit(VirtualAddr + k);
  end;
end;


//------------------------------------------------------------------------------
//** TSettings
//------------------------------------------------------------------------------
procedure TSettings.Create(FileName: String);
var
  i: Integer;
begin
  Self.FileName := FileName;

  for i := CMD_FIRST to CMD_LAST do
  begin
    BreakPoints[i].Addr := Cardinal(-1);
    BreakPoints[i].Pattern[0] := 0;
  end;
end;
//------------------------------------------------------------------------------
function TSettings.Load: Boolean;
var
  F: TextFile;
  T, Attrib: String;
  i: Integer;
begin
  Result := True;
  FileMode := fmOpenRead or fmShareDenyNone;
  AssignFile(F, FileName);
  try
    Reset(F);
    try
      while not Eof(F) do
      begin
        Readln(F, T);
        T := Trim(T);
        if T = '' then Continue;
        T := UpperCase(T);
        T := ReplaceText(T, ' ', '');
        Attrib := LeftStr(T, 11);

        for i := CMD_FIRST to CMD_LAST do
        begin
          if Attrib = SettingsAttrib[i, 1] then
            SetBPA(i, T)
          else if Attrib = SettingsAttrib[i, 2] then
            SetBPT(i, T);
        end;

      end;

      for i := CMD_FIRST to CMD_LAST do
        BPOriginal[i] := BreakPoints[i].Addr;

    finally
      CloseFile(F);
    end;
  except
    Result := False;
  end;
end;
//------------------------------------------------------------------------------
function TSettings.GetAddrText(Index: Integer): String;
begin
  Result := IntToHex(Breakpoints[Index].Addr, 8);
end;
//------------------------------------------------------------------------------
function TSettings.GetPatternText(Index: Integer): String;
var
  i: Integer;
  P: PByte;
begin
  Result := '';
  P := @(BreakPoints[Index].Pattern[0]);

  for i := 1 to P^ do Result := Result + IntToHex(P[i], 2);
end;
//------------------------------------------------------------------------------
function TSettings.Save: Boolean;
var
  Change: Boolean;
  i: Integer;
  F: TextFile;
  T: String;
begin
  Result := True;
  try
    Change := False;

    for i := CMD_FIRST to CMD_LAST do
      if BPOriginal[i] <> BreakPoints[i].Addr then
      begin
        Change := True;
        Break;
      end;

    if Change then
    begin
      FileMode := fmOpenWrite or fmShareDenyNone;
      AssignFile(F, FileName);
      Rewrite(F);
      try

        for i := CMD_FIRST to CMD_LAST do
        begin
          T := SettingsAttrib[i, 1] + Format('%.8X', [BreakPoints[i].Addr]);
          Writeln(F, T);
          T := SettingsAttrib[i, 2] + GetPatternText(i);
          Writeln(F, T);
          WriteLn(F);
        end;

      finally
        CloseFile(F);
      end;

      for i := CMD_FIRST to CMD_LAST do
        BPOriginal[i] := BreakPoints[i].Addr;
    end;
  except
    Result := False;
  end;
end;
//------------------------------------------------------------------------------
procedure TSettings.SetBPA(Index: Integer; T: String);
begin
  T := '$' + RightStr(T, Length(T) - 11);
  BreakPoints[Index].Addr := StrToInt(T);
end;
//------------------------------------------------------------------------------
procedure TSettings.SetBPT(Index: Integer; T: String);
var
  Count, i, k: Integer;
  BStr: String;
  P: PByte;
begin
  P := @(BreakPoints[Index].Pattern[0]);
  T := RightStr(T, Length(T) - 11);
  Count := Length(T) shr 1;

  if Count > 64 then Count := 64;

  P[0] := Count;
  k := 1;

  for i := 1 to Count do
  begin
    BStr := '$' + MidStr(T, k, 2);
    P[i] := StrToInt(BStr);
    Inc(k, 2);
  end;
end;

//==============================================================================
//** TSettingsDialog
//------------------------------------------------------------------------------
procedure TSettingsDialog.BtnBrowse_OnClick(Sender: TObject);
begin
  if Path.Text = '' then
    OpenDialog.InitialDir := '.'
  else
    OpenDialog.FileName := Path.Text;

  if OpenDialog.Execute(Handle) then
  begin
    Path.Text := OpenDialog.FileName;
    ThaiClientExe := Path.Text;
  end;
end;
//------------------------------------------------------------------------------
procedure TSettingsDialog.BtnSave_OnClick(Sender: TObject);
begin
  if not Settings.Save then
    Client.SetErrMsg('!! CAN NOT SAVE SETTINGS FILE.');
end;
//------------------------------------------------------------------------------
procedure TSettingsDialog.BtnUpdate_OnClick(Sender: TObject);
var
  Scan: TScanBuffer;
  PatternSize: Cardinal;
  i: Integer;
begin
  if ThaiClientExe = '' then Exit;

  Scan.Create(ThaiClientExe);

  if not Scan.Created then
  begin
    Client.SetErrMsg('!! CAN NOT SCAN BREAKPOINTS.');
    Exit;
  end;

  try
    for i := CMD_FIRST to CMD_LAST do
    begin
      with Settings.BreakPoints[i] do
      begin
        PatternSize := Pattern[0];

        if (Addr <> Cardinal(-1)) and (PatternSize <> 0) then
          Addr := Scan.Search(@Pattern[1], PatternSize);

        if (Addr < $00420000) or ((Addr > $007FFFFF)) then
          Addr := Cardinal(-1);

        if Addr <> Cardinal(-1) then
        begin
          BPCtrls[i].Text := Settings.GetAddrText(i);
          BPCtrls[i].Font.Color := UpdateColor[True];
        end
        else
        begin
          BPCtrls[i].Text := 'FFFFFFFF';
          BPCtrls[i].Font.Color := UpdateColor[False];
        end;
      end;
    end;
  except
    Client.SetErrMsg('!! CAN NOT UPDATE SETTINGS FILE.');
  end;
  Scan.Free;
end;
//------------------------------------------------------------------------------
procedure TSettingsDialog.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  Path.Text := ThaiClientExe;
  BPCtrls[CMD_SEND1] := BP1;
  BPCtrls[CMD_RECV1] := BP2;
  BPCtrls[CMD_SEND2] := BP3;
  BPCtrls[CMD_RECV2] := BP4;
  PTCtrls[CMD_SEND1] := Pattern1;
  PTCtrls[CMD_RECV1] := Pattern2;
  PTCtrls[CMD_SEND2] := Pattern3;
  PTCtrls[CMD_RECV2] := Pattern4;

  for i := CMD_FIRST to CMD_LAST do
  begin
    BPCtrls[i].Font.Color := clWindowText;
    BPCtrls[i].Text := Settings.GetAddrText(i);
    PTCtrls[i].Text := Settings.GetPatternText(i);
  end;

  BtnUpdate.Font.Color := clWindowText;
end;


//------------------------------------------------------------------------------
procedure SetThaiClientExePath;
var
  Reg: TRegistry;
  Path: String;
begin
  Path := '';
  Reg := TRegistry.Create;
  Reg.RootKey := HKEY_CURRENT_USER;

  if Reg.OpenKeyReadOnly('\Software\KRGSoft\YBOnline\YBOnline\') then
    if Reg.ValueExists('Path') then
      Path := Reg.GetDataAsString('Path') + '\client\ThaiClient.exe';

  if Path = '' then
  begin
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    if Reg.OpenKeyReadOnly('\Software\AsiaSoft\Yulgang\') then
      if Reg.ValueExists('Path') then
        Path := Reg.GetDataAsString('Path') + '\client\ThaiClient.exe';
  end;

  Reg.Free;
  ThaiClientExe := Path;
end;


//------------------------------------------------------------------------------
initialization

SetThaiClientExePath

end.
